#include <linux/sched.h>
#include <linux/elf.h>
#include <linux/kernel.h>
#include <linux/mm.h>

#include <sys/stat.h>
#include <errno.h>
#include <string.h>
#include <asm/segment.h>

#define MAX_ARG_PAGES 32

static unsigned long *create_tables(char *p, int argc, int envc)
{
    unsigned long *argv, *envp;
    unsigned long *sp;

    sp = (unsigned long *)(0xfffffffc & (unsigned long)p);  // 4字节对齐
    sp -= envc + 1;
    envp = sp;
    sp -= argc + 1;
    argv = sp;
    put_fs_long((unsigned long)envp, --sp);
    put_fs_long((unsigned long)argv, --sp);
    put_fs_long((unsigned long)argc, --sp);
    while (envc-- > 0) {
        put_fs_long((unsigned long)p, envp++);
        while (get_fs_byte(p++)); // 找到下一个字符串的首地址
    }
    put_fs_long(0, envp);
    while (argc-- > 0) {
        put_fs_long((unsigned long)p, argv++);
        while (get_fs_byte(p++));
    }
    put_fs_long(0, argv);
    return sp;
}

static int count(char **argv)
{
    int i = 0;
    char **tmp = argv;

    if (tmp)
        while (get_fs_long((unsigned long *)(tmp++)))
            i++;

    return i;
}

static unsigned long copy_strings(int argc, char **argv, unsigned long *page, unsigned long p)
{
    char *tmp, *pag = NULL;
    int len, offset = 0;

    while (argc-- > 0) {
        // 获取字符串的首地址
        tmp = (char *)get_fs_long(((unsigned long *)argv) + argc);
        if (!tmp)
            panic("argc is wrong");
        // 计算字符串的长度
        len = 0;
        do {
            len++;
        } while (get_fs_byte(tmp++));
        if (p - len < 0) {
            return 0;
        }
        // 将字符串写入页面
        while (len) {
            --p;
            --tmp;
            --len;
            if (--offset < 0) {
                offset = p % PAGE_SIZE;
                pag = (char *)page[p / PAGE_SIZE];
                if (!pag) {
                    page[p / PAGE_SIZE] = get_free_page();
                    pag = (char *)page[p / PAGE_SIZE];
                    if (!pag)
                        return 0;
                }
            }
            *(pag + offset) = get_fs_byte(tmp);
        }
    }
    return p;
}

static unsigned long change_ldt(unsigned long text_size, unsigned long *page)
{
    unsigned long code_limit, data_limit, data_base;
    int i;

    code_limit = text_size + PAGE_SIZE - 1;
    code_limit &= 0xFFFFF000;
    data_limit = 0x4000000;
    set_limit(current->ldt[1], code_limit);
    set_limit(current->ldt[2], data_limit);
    data_base = get_base(current->ldt[2]);
    data_base += data_limit;
    for (i = MAX_ARG_PAGES - 1; i >= 0; i--) {
        data_base -= PAGE_SIZE;
        if (page[i])
            put_page(page[i], data_base);
    }
    return data_limit;
}

int do_execve(unsigned long *eip, long tmp, char *filename, char **argv, char **envp)
{
    struct elfhdr elf_ex;
    struct m_inode *inode;
    struct buffer_head *bh;
    struct elf_phdr *elf_phdata;
    int i, argc, envc;
    int e_uid, e_gid;
    unsigned long page[MAX_ARG_PAGES];                  // 页面地址
    unsigned long p = PAGE_SIZE * MAX_ARG_PAGES - 4;    // 栈地址
    unsigned long base;
    unsigned int elf_entry, elf_brk;
    unsigned int start_code, end_code, end_data;
    int retval;

    if ((0xffff & eip[1]) != 0x000f)
        panic("execve called from supervisor mode");
    for (i = 0; i < MAX_ARG_PAGES; i++)
        page[i] = 0;

    inode = namei(filename);
    if (!inode)
        return -ENOENT;
    
    // 必须是普通文件
    if (!S_ISREG(inode->i_mode)) {
        return -EACCES;
    }
    
    // 必须有执行权限
    i = inode->i_mode;
    e_uid = (i & S_ISUID) ? inode->i_uid : current->euid;
    e_gid = (i & S_ISGID) ? inode->i_gid : current->egid;
    if (current->euid == inode->i_uid)
        i >>= 6;
    else if (current->egid == inode->i_gid)
        i >>= 3;
    if (!(i & 1) && !((inode->i_mode & 0111) && suser()))   // 必须是可执行文件
        return -ENOEXEC;

    // 读取文件头和程序头
    bh = bread(inode->i_dev, inode->i_zone[0]);
    if (!bh)
        return -EACCES;
    
    elf_ex = *((struct elfhdr *)bh->b_data);

    // 检查elf魔数
    if (elf_ex.e_ident[0] != 0x7f ||
        strncmp((char *)&elf_ex.e_ident[1], "ELF",3) != 0)
        return -ENOEXEC;

    // 必须是可执行文件（不能是目标文件或动态库），CPU平台是x86
    if(elf_ex.e_type != ET_EXEC || elf_ex.e_machine != EM_386)
        return -ENOEXEC;

    elf_phdata = (struct elf_phdr *)(bh->b_data + elf_ex.e_phoff);
    if (elf_phdata->p_type == PT_LOAD) {
        start_code = elf_phdata->p_vaddr;
        end_code = elf_phdata->p_vaddr + elf_phdata->p_filesz;
        end_data = end_code;
        elf_brk = elf_phdata->p_vaddr + elf_phdata->p_memsz;
    }
    elf_entry = (unsigned int)elf_ex.e_entry - ELF_START_MMAP;
    brelse(bh);
    
    argc = count(argv);
    envc = count(envp);
    p = copy_strings(argc, argv, page, p);
    p = copy_strings(envc, envp, page, p);
    if (!p) {
        retval = -ENOMEM;
        goto exec_error;
    }

    base = get_base(current->ldt[1]);   // 代码段基地址
    free_page_tables(base, get_limit(0x0f));
    free_page_tables(base, get_limit(0x17));
    p += change_ldt(end_code - start_code, page) - MAX_ARG_PAGES * PAGE_SIZE;
    p = (unsigned long)create_tables((char *)p, argc, envc);

    current->brk = elf_brk - ELF_START_MMAP + base;
    current->end_code = end_code - ELF_START_MMAP + base;
    current->end_data = end_data - ELF_START_MMAP + base;
    current->start_stack = p;
    current->euid = e_uid;
    current->egid = e_gid;

    eip[0] = elf_entry; // eip
    eip[3] = p;         // esp
    return 0;
exec_error:
    iput(inode);
    for (i = 0; i < MAX_ARG_PAGES; i++)
        free_page(page[i]);
    return retval;
}
